﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FunctionGraph
{
    class InvalidExpressionException : Exception
    {
        public InvalidExpressionException(string message)
        :base(message)
        { 
        }
    }
    class CodeGenResult
    {
        public string ExprId;
        public int Register;
        public CodeGenResult(string id, int reg)
        {
            ExprId = id;
            Register = reg;
        }
    }
    class CodeGeneratorVisitor : ExpressionVisitor<CodeGenResult>
    {
        Dictionary<string, int> exprMap = new Dictionary<string, int>();
        internal Executable code = new Executable();
        internal int registerCount = 2;

        int AllocReg()
        {
            return registerCount++;
        }

        public override CodeGenResult VisitConstant(ConstantNode constantNode)
        {
            var id = string.Format("${0:X16}$", BitConverter.DoubleToInt64Bits(constantNode.Value));
            int val;
            if (exprMap.TryGetValue(id, out val))
            {
                return new CodeGenResult(id, val);
            }
            code.Constants.Add(constantNode.Value);
            exprMap[id] = -code.Constants.Count;
            return new CodeGenResult(id, -code.Constants.Count);
        }

        public override CodeGenResult VisitVariable(VariableNode variableNode)
        {
            if (variableNode.VariableName == "x")
                return new CodeGenResult("x", 0);
            else if (variableNode.VariableName == "y")
                return new CodeGenResult("y", 1);
            else
                throw new InvalidExpressionException("unkown identifier: " + variableNode.VariableName);
        }

        public override CodeGenResult VisitBinary(BinaryNode binaryNode)
        {
            var left = binaryNode.Left.Accept(this);
            var right = binaryNode.Right.Accept(this);
            var id = string.Format("@{0}({1},{2})", binaryNode.Operator, left.ExprId, right.ExprId);
            int reg;
            if (exprMap.TryGetValue(id, out reg))
            {
                return new CodeGenResult(id, reg);
            }
            reg = AllocReg();
            code.Instructions.Add(new Instruction(binaryNode.Operator, reg, left.Register, right.Register));
            return new CodeGenResult(id, reg);
        }

        public override CodeGenResult VisitUnary(UnaryNode unaryNode)
        {
            var child = unaryNode.Child.Accept(this);
            var id = string.Format("@{0}({1})", unaryNode.Operator, child.ExprId);
            int reg;
            if (exprMap.TryGetValue(id, out reg))
            {
                return new CodeGenResult(id, reg);
            }
            reg = AllocReg();
            code.Instructions.Add(new Instruction(unaryNode.Operator, reg, child.Register));
            return new CodeGenResult(id, reg);
        }
    }

    class CodeGenerator
    {
        public static Executable GenerateCode(ExpressionNode expr)
        {
            var visitor = new CodeGeneratorVisitor();
            expr.Accept(visitor);
            var code = visitor.code;
            foreach (var instr in code.Instructions)
            {
                instr.Result = TranslateRegister(instr.Result, visitor.registerCount);
                instr.Operand1 = TranslateRegister(instr.Operand1, visitor.registerCount);
                instr.Operand2 = TranslateRegister(instr.Operand2, visitor.registerCount);
            }
            code.RegisterCount = visitor.registerCount + code.Constants.Count;
            return code;
        }

        private static int TranslateRegister(int reg, int regCount)
        {
            if (reg < 0)
                return -1 - reg + regCount;
            else
                return reg;
        }
    }
}
